#include <nice2d/tile.h>
#include <nice2d/bitmask.h>
#include <nice2d/serializer.h>
#include <nice2d/deserializerfactory.h>

#define TILE_VISIBLE  0
#define TILE_WALKABLE 1

class TileData : public sf::Vector2i
{
public:
    TileData(Tile &inputTile, float size):
        sideLength(size),
        tile(inputTile)
    {
        bitmask.setBit(TILE_WALKABLE,true);
    }

    sf::Vector2i index;
    float sideLength = 48.0f;
    Bitmask bitmask;

    int type = 0;
    int resourceId = 0;

    SpritePointer sprite;
    sf::Color tileColor;

    void updateHexPosition(int x, int y)
    {
        if (x % 2 == 0) {
            tile.setPosition(x * (1.5 * sideLength), 2 * 0.86602540f * sideLength * y);
        } else {
            tile.setPosition(x * (1.5 * sideLength),
                             2.0 * 0.86602540f * sideLength * y + 0.86602540f * sideLength);
        }
    }

    void updateGridPosition(int x, int y)
    {
        tile.setPosition(x * sideLength * 2, y * sideLength * 2);
    }

    void updateGridMalPosition(int x, int y)
    {
        if (x % 2 == 0)
            tile.setPosition(x * sideLength * 2, y * sideLength * 2);
        else
            tile.setPosition(x * sideLength * 2, y * sideLength * 2 + sideLength);
    }
private:
    Tile &tile;
};

Tile::Tile(uint16_t x, uint16_t y, float sideLength, TileMapType type):
    data(new TileData(*this, sideLength))
{
    data->index = {x,y};

    if (type == TileMapType_Hex) {
        const float s = 1.f * (sideLength);
        const float h = 0.5f * (sideLength);        //sin(30)
        const float r = 0.86602540f * (sideLength); //cos(30)

        setPointCount(6);
        setPoint(0, sf::Vector2f(0.f, -s));
        setPoint(1, sf::Vector2f(-r, -h));
        setPoint(2, sf::Vector2f(-r, +h));
        setPoint(3, sf::Vector2f(0.f, +s));
        setPoint(4, sf::Vector2f(+r, +h));
        setPoint(5, sf::Vector2f(+r, -h));
        rotate(30.0f);
        data->updateHexPosition(x, y);
    } else if (type == TileMapType_Grid) {
        setPointCount(4);
        setPoint(0, sf::Vector2f(-sideLength, -sideLength));
        setPoint(1, sf::Vector2f(sideLength, -sideLength));
        setPoint(2, sf::Vector2f(sideLength, sideLength));
        setPoint(3, sf::Vector2f(-sideLength, sideLength));
        data->updateGridPosition(x, y);
    } else if (type == TileMapType_MalGrid) {
        setPointCount(4);
        setPoint(0, sf::Vector2f(-sideLength, -sideLength));
        setPoint(1, sf::Vector2f(sideLength, -sideLength));
        setPoint(2, sf::Vector2f(sideLength, sideLength));
        setPoint(3, sf::Vector2f(-sideLength, sideLength));
        data->updateGridMalPosition(x, y);
    }

    setVisible(true);
}

Tile::~Tile()
{
}

sf::Vector2i Tile::getTileIndex() const
{
    return data->index;
}

void Tile::setTileType(int type)
{
    data->type = type;
}

int Tile::getTileType() const
{
    return data->type;
}

void Tile::setTileResourceId(int resourceId)
{
    data->resourceId = resourceId;
}

int Tile::getTileResourceId() const
{
    return data->resourceId;
}

void Tile::setWalkable(bool walkable)
{
    data->bitmask.setBit(TILE_WALKABLE,walkable);
}

bool Tile::isWalkable() const
{
    return data->bitmask.contain(TILE_WALKABLE);
}

void Tile::setSprite(SpritePointer sprite)
{
    data->sprite = sprite;
}

void Tile::clearSprite()
{
    data->sprite.reset();
}

SpritePointer Tile::getSprite() const
{
    return data->sprite;
}

void Tile::setVisible(bool visible)
{
    data->bitmask.setBit(TILE_VISIBLE, visible);
}

bool Tile::isVisible()const
{
    return data->bitmask.contain(TILE_VISIBLE);
}

PTree Tile::serialize()
{
    PTree node;
    node.add("type", getTileType());
    node.add("resourceId", getTileResourceId());
    node.add("visible", isVisible());
    node.add("isWalkable", isWalkable());
    node.add_child("color",serializeObject(getFillColor()));
    node.add_child("outlineColor",serializeObject(getOutlineColor()));
    node.add("outlineThickness", getOutlineThickness());
    return node;
}

void Tile::deserialize(const PTree &node)
{
    setTileType(node.get<int32_t>("type"));
    setTileResourceId(node.get<int32_t>("resourceId"));
    setVisible(node.get<bool>("visible"));
    setWalkable(node.get<bool>("isWalkable"));

    auto colorNode = node.get_child("color");
    setFillColor(deserializeColor(colorNode));
    auto outlineColorNode = node.get_child("outlineColor");
    setOutlineColor(deserializeColor(outlineColorNode));
    setOutlineThickness(node.get<float>("outlineThickness"));
}
